package main

import (
	"fmt"
	"reflect"
)

// 接口
/**
接口是和调用方的一种约定，它是一个高度抽象的类型，不用和具体的实现细节绑定在一起。接口要做的是定义好约定，告诉调用方自己可以做什么，但不用知道它的内部实现，这和我们见到的具体的类型如 int、map、slice 等不一样。
接口的定义和结构体稍微有些差别，虽然都以 type 关键字开始，但接口的关键字是 interface，表示自定义的类型是一个接口。也就是说 Stringer 是一个接口，它有一个方法 String() string，整体如下面的代码所示：
*/

// type 和 interface 定义一个接口
type Stringer interface {
	/**
	定义一个Stringer接口，接口中有个需要实现的方法String，返回值是string类型
	针对 Stringer 接口来说，它会告诉调用者可以通过它的 String() 方法获取一个字符串，这就是接口的约定。至于这个字符串怎么获得的，长什么样，接口不关心，调用者也不用关心，因为这些是由接口实现者来做的。
	*/
	String() string
}

// 实现接口
type Person struct {
	name string
	age  uint
}

type human struct {
	name string
	age  uint
}

// 只要在Person类中定义一个String方法，方法的签名（名称，参数，返回值）都和接口中的方法一致，就说这个类型实现类接口中的方法
// 注意：如果一个接口有多个方法，那么需要实现接口的每个方法才算是实现了这个接口。
func (p Person) String() string {
	//fmt.Println("person.name:",p.name,"person.age",p.age)
	return fmt.Sprintf("the name is %s,age is %d", p.name, p.age)
}
func (p *human) String() string {
	//fmt.Println("person.name:",p.name,"person.age",p.age)
	return fmt.Sprintf("the name is %s,age is %d", p.name, p.age)
}

// 实现了 Stringer 接口后就可以使用了。首先我先来定义一个可以打印 Stringer 接口的函数，如下所示
func printStringer(s Stringer) {
	fmt.Println(s.String())
}
func printFmtStringer(s fmt.Stringer) {
	fmt.Println(s.String())
}

//func main() {
//	p := Person{"张小凡",27}
//	//fmt.Println(p)
//	printStringer(p)
//	printFmtStringer(p)
//	//把变量 p 的指针作为实参传给 printString 函数也是可以的，编译运行都正常。这就证明了以值类型接收者实现接口的时候，不管是类型本身，还是该类型的指针类型，都实现了该接口。
//	/**
//	如果方法的接受者是值类型，则传递值和值的指针，方法都是可以被正确执行的
//	 */
//	printFmtStringer(&p)
//	//fmt.Println("point 测试")
//	// 方法接受者是指针类型，传递给他指针类型
//	printStringPoint(&p)
//	//  方法接受者是指针类型，传递给他值类型 报错，编译不通过
//	//printStringPoint(p)  // Person does not implement PointStringer (StringPoint method has pointer receiver)
//	/**
//	结论：
//	以指针类型接收者实现接口的时候，只有对应的指针类型才被认为实现了该接口。
//	- 当值类型作为接收者时，person 类型和*person类型都实现了该接口。
//	- 当指针类型作为接收者时，只有*person类型实现了该接口。
//	可以发现，实现接口的类型都有*person，这也表明指针类型比较万能，不管哪一种接收者，它都能实现该接口。
//	 */
//}

// 值类型接受者 和 指针类型接收者

/**
我们已经知道，如果要实现一个接口，必须实现这个接口提供的所有方法
，而且在上节课讲解方法的时候，我们也知道定义一个方法，有值类型接收者和指针类型接收者两种。
二者都可以调用方法，因为 Go 语言编译器自动做了转换，所以值类型接收者和指针类型接收者是等价的。
但是在接口的实现中，值类型接收者和指针类型接收者不一样，下面我会详细分析二者的区别。
*/
// 定义一个PointStringer接口，包含一个StringPoint方法
type PointStringer interface {
	StringPoint() string
}

// 让Person实现PointStringer接口,只是接口的接受者是指针类型
func (p *Person) StringPoint() string {
	return fmt.Sprintf("point,the name is %s,age is %d", p.name, p.age)
}

// 定义一个方法，参数类型是实现了PointStringer的
func printStringPoint(ps PointStringer) {
	//fmt.Println("string point")
	fmt.Println(ps.StringPoint())
}

// 工厂函数
/**
工厂函数一般用于创建自定义的结构体，便于使用者调用，我们还是以 person 类型为例，用如下代码进行定义：
*/

func NewPerson(name string) *Person {
	return &Person{name: name}
}

//func main() {
//	p1:=NewPerson("张三")
//	fmt.Println(p1)
//}

/**
工厂函数也可以用来创建一个接口，它的好处就是可以隐藏内部具体类型的实现，让调用者只需关注接口的使用即可。
现在我以 errors.New 这个 Go 语言自带的工厂函数为例，演示如何通过工厂函数创建一个接口，并隐藏其内部实现，如下代码所示：
error 接口
// The error built-in interface type is the conventional interface for
// representing an error condition, with the nil value representing no error.
type error interface {
	Error() string
}
*/

// 首先定义一个结构体
type errorMessage struct {
	s string
}

//// 让这个结构体实现Error接口
//func (e errorMessage) Error() string {
//	return e.s
//}
//
//// 创建一个工厂函数，传人字符串，返回一个error接口类型
//func newError(s string) error {
//	return errorMessage{s: s}
//}

// 也可以方法接受者指针类型，传递指针
// 让这个结构体实现Error接口
func (e *errorMessage) Error() string {
	return e.s
}

// 创建一个工厂函数，传人字符串，返回一个error接口类型
func newError(s string) error {
	return &errorMessage{s: s}
}

//func main() {
//	e := newError("this is an error")
//	fmt.Println(e.Error())
//	fmt.Println(e)
//}

// 类型断言
/**
有了接口和实现接口的类型，就会有类型断言。类型断言用来判断一个接口的值是否是实现该接口的某个具体类型。
*/

/**
func (p Person) String() string {
	//fmt.Println("person.name:",p.name,"person.age",p.age)
	return fmt.Sprintf("the name is %s,age is %d",p.name,p.age)
}
func (p *human) String() string {
	//fmt.Println("person.name:",p.name,"person.age",p.age)
	return fmt.Sprintf("the name is %s,age is %d",p.name,p.age)
}
可以看到，在上面到代码中 person和human都实现了String接口

*/

func main() {
	p1 := &Person{name: "张三"}
	var s Stringer
	s = p1
	// 这里返回的 p2 已经是 *person 类型了，也就是在类型断言的时候，同时完成了类型转换。
	p2 := s.(*Person) // 判断p1是实现了Stringer的Person结构体的指针类型
	fmt.Println(p2, ":", *p2)
	//y := s.(Person) // 判断p1是实现了Stringer的Person结构体的值类型,不是，则报错
	//fmt.Println(y,":",y)

	p := Person{name: "小米"}
	s = p
	// p3 := s.(*Person) // 如果是值类型，编译通过，运行不通过 panic: interface conversion: main.Stringer is main.Person, not *main.Person  方法接受者是值类型，传递指针类型报错
	p3 := s.(Person)         // 如果赋值的p是值类型，这里必须写值类型，如果赋值的p是指针类型，则必须写指针类型
	fmt.Println(p3, ":", p3) //通过，说明验证的时候，传人是什么类型，返回的就是什么类型
	/**
	如上所示，接口变量 s 称为接口 fmt.Stringer 的值，它被 p1 赋值。
	然后使用类型断言表达式 s.(person)，尝试返回一个 p2。如果接口的值 s 是一个person，
	那么类型断言正确，可以正常返回 p2。如果接口的值 s 不是一个 *person，那么在运行时就会抛出异常，程序终止运行。
	*/
	//p5 := s.(*human) //  main.Stringer is main.Person, not *main.human 判断p是human指针类型，不是报错
	//fmt.Println(p5)
	//p6 := s.(human) //  human指针类型实现类string接口，human值类型未实现，报错
	//fmt.Println(p6)

	p7, ok := s.(*human) // go提供的对于类型断言异常的处理
	if ok {
		fmt.Println(p7)
	} else {
		fmt.Println("s不是一个*human")
	}

	// 另一种类型判断方法，利用反射
	fmt.Println(reflect.TypeOf(p1), reflect.TypeOf(p1).String() == "*main.Person")
	fmt.Println()

	//fmt.Println(p1.(Person))
}

type empty interface {
}
